﻿/**
 * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

CKEDITOR.plugins.add('richcombo', {
    requires: 'floatpanel,listblock,button',

    beforeInit: function (editor) {
        editor.ui.addHandler(CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler);
    }
});

(function () {
    var template = '<span id="{id}"' +
        ' class="cke_combo cke_combo__{name} {cls}"' +
        ' role="presentation">' +
        '<span id="{id}_label" class="cke_combo_label">{label}</span>' +
        '<a class="cke_combo_button" hidefocus=true title="{title}" tabindex="-1"' +
        (CKEDITOR.env.gecko && !CKEDITOR.env.hc ? '' : '" href="javascript:void(\'{titleJs}\')"') +
        ' hidefocus="true"' +
        ' role="button"' +
        ' aria-labelledby="{id}_label"' +
        ' aria-haspopup="true"';

    // Some browsers don't cancel key events in the keydown but in the
    // keypress.
    // TODO: Check if really needed.
    if (CKEDITOR.env.gecko && CKEDITOR.env.mac)
        template += ' onkeypress="return false;"';

    // With Firefox, we need to force the button to redraw, otherwise it
    // will remain in the focus state.
    if (CKEDITOR.env.gecko)
        template += ' onblur="this.style.cssText = this.style.cssText;"';

    template +=
        ' onkeydown="return CKEDITOR.tools.callFunction({keydownFn},event,this);"' +
        ' onfocus="return CKEDITOR.tools.callFunction({focusFn},event);" ' +
        (CKEDITOR.env.ie ? 'onclick="return false;" onmouseup' : 'onclick') + // #188
        '="CKEDITOR.tools.callFunction({clickFn},this);return false;">' +
        '<span id="{id}_text" class="cke_combo_text cke_combo_inlinelabel">{label}</span>' +
        '<span class="cke_combo_open">' +
        '<span class="cke_combo_arrow">' +
        // BLACK DOWN-POINTING TRIANGLE
        (CKEDITOR.env.hc ? '&#9660;' : CKEDITOR.env.air ? '&nbsp;' : '') +
        '</span>' +
        '</span>' +
        '</a>' +
        '</span>';

    var rcomboTpl = CKEDITOR.addTemplate('combo', template);

    /**
     * Button UI element.
     *
     * @readonly
     * @property {String} [='richcombo']
     * @member CKEDITOR
     */
    CKEDITOR.UI_RICHCOMBO = 'richcombo';

    /**
     * @class
     * @todo
     */
    CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass({
        $: function (definition) {
            // Copy all definition properties to this object.
            CKEDITOR.tools.extend(this, definition,
                // Set defaults.
                {
                    // The combo won't participate in toolbar grouping.
                    canGroup: false,
                    title: definition.label,
                    modes: {wysiwyg: 1},
                    editorFocus: 1
                });

            // We don't want the panel definition in this object.
            var panelDefinition = this.panel || {};
            delete this.panel;

            this.id = CKEDITOR.tools.getNextNumber();

            this.document = (panelDefinition.parent && panelDefinition.parent.getDocument()) || CKEDITOR.document;

            panelDefinition.className = 'cke_combopanel';
            panelDefinition.block = {
                multiSelect: panelDefinition.multiSelect,
                attributes: panelDefinition.attributes
            };
            panelDefinition.toolbarRelated = true;

            this._ = {
                panelDefinition: panelDefinition,
                items: {}
            };
        },

        proto: {
            renderHtml: function (editor) {
                var output = [];
                this.render(editor, output);
                return output.join('');
            },

            /**
             * Renders the combo.
             *
             * @param {CKEDITOR.editor} editor The editor instance which this button is
             * to be used by.
             * @param {Array} output The output array to which append the HTML relative
             * to this button.
             */
            render: function (editor, output) {
                var env = CKEDITOR.env;

                var id = 'cke_' + this.id;
                var clickFn = CKEDITOR.tools.addFunction(function (el) {
                    // Restore locked selection in Opera.
                    if (selLocked) {
                        editor.unlockSelection(1);
                        selLocked = 0;
                    }
                    instance.execute(el);
                }, this);

                var combo = this;
                var instance = {
                    id: id,
                    combo: this,
                    focus: function () {
                        var element = CKEDITOR.document.getById(id).getChild(1);
                        element.focus();
                    },
                    execute: function (el) {
                        var _ = combo._;

                        if (_.state == CKEDITOR.TRISTATE_DISABLED)
                            return;

                        combo.createPanel(editor);

                        if (_.on) {
                            _.panel.hide();
                            return;
                        }

                        combo.commit();
                        var value = combo.getValue();
                        if (value)
                            _.list.mark(value);
                        else
                            _.list.unmarkAll();

                        _.panel.showBlock(combo.id, new CKEDITOR.dom.element(el), 4);
                    },
                    clickFn: clickFn
                };

                function updateState() {
                    // Don't change state while richcombo is active (#11793).
                    if (this.getState() == CKEDITOR.TRISTATE_ON)
                        return;

                    var state = this.modes[editor.mode] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;

                    if (editor.readOnly && !this.readOnly)
                        state = CKEDITOR.TRISTATE_DISABLED;

                    this.setState(state);
                    this.setValue('');

                    // Let plugin to disable button.
                    if (state != CKEDITOR.TRISTATE_DISABLED && this.refresh)
                        this.refresh();
                }

                // Update status when activeFilter, mode, selection or readOnly changes.
                editor.on('activeFilterChange', updateState, this);
                editor.on('mode', updateState, this);
                editor.on('selectionChange', updateState, this);
                // If this combo is sensitive to readOnly state, update it accordingly.
                !this.readOnly && editor.on('readOnly', updateState, this);

                var keyDownFn = CKEDITOR.tools.addFunction(function (ev, element) {
                    ev = new CKEDITOR.dom.event(ev);

                    var keystroke = ev.getKeystroke();

                    // ARROW-DOWN
                    // This call is duplicated in plugins/toolbar/plugin.js in itemKeystroke().
                    // Move focus to the first element after drop down was opened by the arrow down key.
                    if (keystroke == 40) {
                        editor.once('panelShow', function (evt) {
                            evt.data._.panel._.currentBlock.onKeyDown(40);
                        });
                    }

                    switch (keystroke) {
                        case 13: // ENTER
                        case 32: // SPACE
                        case 40: // ARROW-DOWN
                            // Show panel
                            CKEDITOR.tools.callFunction(clickFn, element);
                            break;
                        default:
                            // Delegate the default behavior to toolbar button key handling.
                            instance.onkey(instance, keystroke);
                    }

                    // Avoid subsequent focus grab on editor document.
                    ev.preventDefault();
                });

                var focusFn = CKEDITOR.tools.addFunction(function () {
                    instance.onfocus && instance.onfocus();
                });

                var selLocked = 0;

                // For clean up
                instance.keyDownFn = keyDownFn;

                var params = {
                    id: id,
                    name: this.name || this.command,
                    label: this.label,
                    title: this.title,
                    cls: this.className || '',
                    titleJs: env.gecko && !env.hc ? '' : (this.title || '').replace("'", ''),
                    keydownFn: keyDownFn,
                    focusFn: focusFn,
                    clickFn: clickFn
                };

                rcomboTpl.output(params, output);

                if (this.onRender)
                    this.onRender();

                return instance;
            },

            createPanel: function (editor) {
                if (this._.panel)
                    return;

                var panelDefinition = this._.panelDefinition,
                    panelBlockDefinition = this._.panelDefinition.block,
                    panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(),
                    namedPanelCls = 'cke_combopanel__' + this.name,
                    panel = new CKEDITOR.ui.floatPanel(editor, panelParentElement, panelDefinition),
                    list = panel.addListBlock(this.id, panelBlockDefinition),
                    me = this;

                panel.onShow = function () {
                    this.element.addClass(namedPanelCls);

                    me.setState(CKEDITOR.TRISTATE_ON);

                    me._.on = 1;

                    me.editorFocus && !editor.focusManager.hasFocus && editor.focus();

                    if (me.onOpen)
                        me.onOpen();

                    // The "panelShow" event is fired assinchronously, after the
                    // onShow method call.
                    editor.once('panelShow', function () {
                        list.focus(!list.multiSelect && me.getValue());
                    });
                };

                panel.onHide = function (preventOnClose) {
                    this.element.removeClass(namedPanelCls);

                    me.setState(me.modes && me.modes[editor.mode] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);

                    me._.on = 0;

                    if (!preventOnClose && me.onClose)
                        me.onClose();
                };

                panel.onEscape = function () {
                    // Hide drop-down with focus returned.
                    panel.hide(1);
                };

                list.onClick = function (value, marked) {

                    if (me.onClick)
                        me.onClick.call(me, value, marked);

                    panel.hide();
                };

                this._.panel = panel;
                this._.list = list;

                panel.getBlock(this.id).onHide = function () {
                    me._.on = 0;
                    me.setState(CKEDITOR.TRISTATE_OFF);
                };

                if (this.init)
                    this.init();
            },

            setValue: function (value, text) {
                this._.value = value;

                var textElement = this.document.getById('cke_' + this.id + '_text');
                if (textElement) {
                    if (!(value || text)) {
                        text = this.label;
                        textElement.addClass('cke_combo_inlinelabel');
                    } else
                        textElement.removeClass('cke_combo_inlinelabel');

                    textElement.setText(typeof text != 'undefined' ? text : value);
                }
            },

            getValue: function () {
                return this._.value || '';
            },

            unmarkAll: function () {
                this._.list.unmarkAll();
            },

            mark: function (value) {
                this._.list.mark(value);
            },

            hideItem: function (value) {
                this._.list.hideItem(value);
            },

            hideGroup: function (groupTitle) {
                this._.list.hideGroup(groupTitle);
            },

            showAll: function () {
                this._.list.showAll();
            },

            add: function (value, html, text) {
                this._.items[value] = text || value;
                this._.list.add(value, html, text);
            },

            startGroup: function (title) {
                this._.list.startGroup(title);
            },

            commit: function () {
                if (!this._.committed) {
                    this._.list.commit();
                    this._.committed = 1;
                    CKEDITOR.ui.fire('ready', this);
                }
                this._.committed = 1;
            },

            setState: function (state) {
                if (this._.state == state)
                    return;

                var el = this.document.getById('cke_' + this.id);
                el.setState(state, 'cke_combo');

                state == CKEDITOR.TRISTATE_DISABLED ?
                    el.setAttribute('aria-disabled', true) :
                    el.removeAttribute('aria-disabled');

                this._.state = state;
            },

            getState: function () {
                return this._.state;
            },

            enable: function () {
                if (this._.state == CKEDITOR.TRISTATE_DISABLED)
                    this.setState(this._.lastState);
            },

            disable: function () {
                if (this._.state != CKEDITOR.TRISTATE_DISABLED) {
                    this._.lastState = this._.state;
                    this.setState(CKEDITOR.TRISTATE_DISABLED);
                }
            }
        },

        /**
         * Represents richCombo handler object.
         *
         * @class CKEDITOR.ui.richCombo.handler
         * @singleton
         * @extends CKEDITOR.ui.handlerDefinition
         */
        statics: {
            handler: {
                /**
                 * Transforms a richCombo definition in a {@link CKEDITOR.ui.richCombo} instance.
                 *
                 * @param {Object} definition
                 * @returns {CKEDITOR.ui.richCombo}
                 */
                create: function (definition) {
                    return new CKEDITOR.ui.richCombo(definition);
                }
            }
        }
    });

    /**
     * @param {String} name
     * @param {Object} definition
     * @member CKEDITOR.ui
     * @todo
     */
    CKEDITOR.ui.prototype.addRichCombo = function (name, definition) {
        this.add(name, CKEDITOR.UI_RICHCOMBO, definition);
    };

})();
